

import sys
import chardet
import os
import re


def read_data_from_binary_file(filename):
    f = open(filename, 'rb')
    list_data = []
    while True:
        t_byte = f.read(1)
        if len(t_byte) == 0:
            break
        else:
            list_data.append(t_byte)

    f.close()
    return list_data


def data_to_text(list_data, data_num_per_line=16, ishex=True):
    if ((data_num_per_line <= 0) or data_num_per_line > len(list_data)):
        data_num_per_line = 16
        print('data_num_per_line out of range,use default value\n')

    s = ''

    if ishex:
        for i in range(len(list_data)):
            s += ('0x%.2X,' % ord(list_data[i]))
            if 0 == (i+1) % data_num_per_line:
                s += '\n'
    else:
        for i in range(len(list_data)):
            s += ('%d,' % ord(list_data[i]))
            if 0 == (i+1) % data_num_per_line:
                s += '\n'

    return s


def file2txt(input_f, name, data_num_per_line, ishex=True):
    list_data = read_data_from_binary_file(input_f)
    s = data_to_text(list_data, data_num_per_line, ishex)
    sz = str(len(list_data))
    x = 'static const unsigned char %s[%s] = {\n%s};\n' % (name, sz, s)
    return x


def file2str(input_f, name, type1, data_num_per_line=16, ishex=True):
    #s += file2txt(input_f, name+'_', self.data_num_per_line)
    list_data = read_data_from_binary_file(input_f)
    s = data_to_text(list_data, data_num_per_line, ishex)
    sz = str(len(list_data))
    vname = f'__{name}_{type1}'
    out = 'static const unsigned char %s[%s] = {\n%s};\n' % (vname, sz, s)
    out += 'addRes(%s, %s, %s, %s);\n' % (type1, name, vname, sz)
    return out



def file2str(input_f, name, type1, data_num_per_line=16, ishex=True):
    #s += file2txt(input_f, name+'_', self.data_num_per_line)
    vname = f'__{name}_{type1}'
    input_f = input_f.replace('\\', '/').replace('//', '/')
    out = f'static const char* {vname} = "{input_f}";\n'
    sz = str(len(input_f))
    sz = f"sizeof({vname})"
    out += 'addRes(%s, %s, %s, %s);\n' % (type1, name, vname, sz)
    return out


class Res:
    def __init__(self, data_num_per_line=16):
        self.files = []
        self.data_num_per_line = data_num_per_line
        pass

    def add_dir(self, input_dir, extlist=None):
        for root, dirs, files in os.walk(input_dir):
            for filenameext in files:
                filename, extension = os.path.splitext(filenameext)
                if extlist is None or extension in extlist:
                    input_f = os.path.join(root, filenameext)
                    self.add_file(input_f, filename)

        return self

    def add_file(self, input_f, name=None):
        if name is None:
            _, filenameext = os.path.split(input_f)
            name, _ = os.path.splitext(filenameext)
        self.files.append((input_f, name))
        return self

    def get_str(self):
        out = ''
        for input_f, name in self.files:
            #s += file2txt(input_f, name+'_', self.data_num_per_line)
            list_data = read_data_from_binary_file(input_f)
            s = data_to_text(list_data, self.data_num_per_line)
            sz = str(len(list_data))
            out += 'unsigned char %s_[%s] = {\n%s};\n' % (name, sz, s)
            out += 'addRes(%s, %s, %s_, %s);\n' % ('DATA', name, name, sz)
        return out

    def write(self, fn):
        mm = os.path.abspath(fn)
        m = re.sub(r'[/\\.:]', '_', mm).upper()
        s = self.get_str()
        x = [
            '#ifndef %s' % m,
            '#define %s' % m,
            '#include "std/res.h"',
            s,
            '#endif // %s' % m,
        ]
        s = '\n'.join(x)
        with open(fn, 'w+') as f_output:
            f_output.write(s)
        return s


def conv_text(text):
    if '"' not in text and 'NULL' != text:
        text = 'MAKERES(%s)' % text

    return text


class StringParam:
    def __init__(self):
        self.id = '-1'
        self.text = 'NULL'


class WindowParam:
    def __init__(self):
        self.className = 'NULL'
        self.windowName = 'NULL'
        self.id = '-1'
        self.x = '0'
        self.y = '0'
        self.width = '0'
        self.height = '0'
        self.style = '0'
        self.exstyle = '0'
        self.font = ''
        self.subs = []

    def print(self, intend=''):
        print(intend+self.className, self.id, self.x,
              self.y, self.width, self.height)
        if self.className in ['EDITTEXT']:
            pass
        for c in self.subs:
            c.print('    ')

    def c_str(self):
        text = conv_text(self.windowName)
        self.style = self.style.replace('NOT ', 'NOT_')
        s = '{%s, %s, %s, %s, %s, %s, %s, %s, %s},\n' % (
            self.className, text, self.id, self.x, self.y, self.width, self.height, self.style, self.exstyle)
        for c in self.subs:
            s += c.c_str()
        return s

    def get_ids(self):
        ids = []
        if self.id != '-1':
            ids.append(self.id)
        for c in self.subs:
            ids += c.get_ids()
        return ids


class MenuParam:
    def __init__(self):
        self.id = '-1'
        self.text = 'NULL'
        self.subs = []

    def print(self, intend=''):
        pass

    def get_ids(self):
        ids = []
        if self.id != '-1':
            ids.append(self.id)
        for c in self.subs:
            ids += c.get_ids()
        return ids

    def c_str(self, indent=0):
        text = conv_text(self.text)
        s = '{%s, %s, %s},\n' % (indent, self.id, text)
        for c in self.subs:
            s += c.c_str(indent+1)
        return s


def read_lines1(path):
    f = open(path, 'r', encoding='utf-16')
    lines = f.readlines()
    f.close()
    return lines


def read_lines(fn1):
    tt = open(fn1, "rb").read()
    dd = chardet.detect(tt)
    dd = dd['encoding']
    if dd is None:
        dd = 'gbk'
    tt = tt.decode(dd, 'ignore')
    tt = tt.replace('\r\n', '\n')
    lines = tt.split('\n')
    return lines


def split_line(line):
    a1 = line.split(' ')
    name = a1[0]
    info = ' '.join(a1[1:]).strip()
    aa = info.split(',')
    i = 0
    while True:
        if i >= len(aa):
            break
        if aa[i].count('"') == 1:
            j = i+1
            while(j < len(aa)):
                bb = aa[j].count('"') == 1
                aa[i] += ',' + aa[j]
                aa.pop(j)
                if bb:
                    break

        i += 1

    return name, aa


sysdef = [
    "IDOK",
    "IDCANCEL",
    "IDABORT",
    "IDRETRY",
    "IDIGNORE",
    "IDYES",
    "IDNO",
    "IDCLOSE",
    "IDHELP",
    "IDTRYAGAIN",
    "IDCONTINUE",
    "IDTIMEOUT",
    "IDTIMEOUT",
    "IDC_STATIC",
    "ID_APP_ABOUT",
    "ID_APP_EXIT",
    "ID_HELP_INDEX",
    "ID_HELP_FINDER",
    "ID_HELP_USING",
    "ID_NEXT_PANE",
    "ID_PREV_PANE",
    "ID_WINDOW_SPLIT",
]


def split_next(s, sub, skip, p):
    if p < 0:
        return '', -1

    while(p < len(s) and s[p] in skip):
        p += 1
    p1 = s.find(sub, p)
    if p1 < 0:
        return s[p:], p
    return s[p:p1], p1+1


def next_tok(s, sub, skip, p):
    if p < 0:
        return '', -1

    while(p < len(s) and s[p] in skip):
        p += 1

    p1 = p
    while(1):
        p1 = s.find(sub, p1)
        if p1 < 0:
            return s[p:].strip(skip), -1
        if s[p:p1].count('"') == 1:
            p1 += 1
            continue

        return s[p:p1].strip(skip), p1+1

    return s[p:p1].strip(skip), p1+1


def next_alltok(s, sub, skip, p):
    out = []
    while(p > 0):
        x, p = next_tok(s, sub, skip, p)
        out.append(x)

    return out


class RcMake:
    def __init__(self):
        pass

    def getline(self):
        line = self.lines[self.i]
        self.i += 1
        return line

    def parser_(self, line):
        pass

    def parse_ctrl(self, line):
        name, p = next_tok(line, ' ', ' \t', 0)
        aa = next_alltok(line, ',', ' \t', p)
        pp = WindowParam()
        pp.className = name
        if len(aa) == 8:
            if name in ['LTEXT', 'CTEXT', 'RTEXT']:
                pp.windowName, pp.id, pp.x, pp.y, pp.width, pp.height, pp.style, pp.exstyle = aa
            else:
                pp.windowName, pp.id, pp.className, pp.style, pp.x, pp.y, pp.width, pp.height = aa
            return pp

        elif len(aa) == 7:
            if name in ['EDITTEXT', 'COMBOBOX', 'LISTBOX']:
                pp.id, pp.x, pp.y, pp.width, pp.height, pp.style, pp.exstyle = aa
            else:
                pp.windowName, pp.id, pp.x, pp.y, pp.width, pp.height, pp.style = aa
            return pp

        elif len(aa) == 6:
            if name in ['EDITTEXT', 'COMBOBOX', 'LISTBOX']:
                pp.id, pp.x, pp.y, pp.width, pp.height, pp.style = aa
            elif name in ['ICON']:
                pp.windowName, pp.id, pp.x, pp.y, pp.width, pp.height = aa
            else:
                pp.windowName, pp.id, pp.x, pp.y, pp.width, pp.height = aa
            return pp
        elif len(aa) == 9:
            pp.windowName, pp.id, pp.className, pp.x, pp.y, pp.width, pp.height, pp.style, pp.exstyle = aa
            return pp
        else:
            print('parse_ctrl error: ', line)

        return None

    def parse_ctrls(self):
        lines = []
        while(self.i < self.nlines):
            line = self.getline()
            if 'END' == line:
                break
            else:
                if line[:8] == '        ' and len(lines) > 0:
                    lines[-1] += line
                else:
                    lines.append(line)

        ctrls = []
        for line in lines:
            line = re.sub(' +', ' ', line)
            x = self.parse_ctrl(line)
            if x is not None:
                if x.id == '5':
                    print(self.i, line)
                ctrls.append(x)

        return ctrls

    def parse_DIALOG(self, line, id1, p):
        line = line.replace('DISCARDABLE', '')
        aa = next_alltok(line, ',', ' ', p)
        if len(aa) != 4:
            return None
        pp = WindowParam()
        pp.className = 'DIALOG'
        pp.id = id1
        pp.x, pp.y, pp.width, pp.height = aa
        while(self.i < self.nlines):
            line = self.getline()
            name, p = next_tok(line, ' ', ' \t', 0)
            if 'EXSTYLE' == name:
                pp.exstyle = line[p:].strip()
            elif 'STYLE' == name:
                pp.style = line[p:].strip()
            elif 'CAPTION' == name:
                pp.windowName = line[p:].strip()
            elif 'FONT' == name:
                pp.font = line[p:].strip()
            elif 'BEGIN' == line:
                pp.subs = self.parse_ctrls()
                return pp
            elif 'END' == line:
                return pp
            else:
                print('error: parse_DIALOG')
                return None

        return pp

    def parse_MENU(self, id='-1', text='NULL'):
        pp = MenuParam()
        pp.id = id
        pp.text = text
        while(self.i < self.nlines):
            line = self.getline()
            name1, p = next_tok(line, ' ', ' \t', 0)
            if 'BEGIN' == name1:
                continue

            if 'END' == name1:
                return pp

            aa = next_alltok(line, ',', ' \t', p)
            if 'POPUP' == name1:
                assert len(aa) > 0
                p1 = self.parse_MENU('-1', aa[0])
                pp.subs.append(p1)

            if 'MENUITEM' == name1:
                p1 = MenuParam()
                assert len(aa) > 0
                if 'SEPARATOR' == aa[0]:
                    p1.id = '-1'
                    p1.text = 'NULL'
                else:
                    assert len(aa) > 1
                    p1.id = aa[1].strip()
                    p1.text = aa[0]

                pp.subs.append(p1)

        print('error: parse_MENU')
        return None

    def parse_STRINGTABLE(self):
        pp = []
        while(self.i < self.nlines):
            line = self.getline()
            name1, p = next_tok(line, ' ', ' \t', 0)
            if 'BEGIN' == name1:
                continue

            if 'END' == name1:
                return pp

            aa, p = next_tok(line, ',', ' \t', p)
            if len(aa) < 2:
                continue
            if aa[0] == '"' and aa[-1] == '"':
                sp = StringParam()
                sp.id = name1
                sp.text = aa
                pp.append(sp)
            else:
                print('error:', self.i, name1, aa)

        print('error: parse_MENU')
        return None

    def parse_file(self, fn):
        self.fn = fn
        lines = read_lines(fn)
        self.lines = lines
        self.i = 0
        self.nlines = len(self.lines)
        self.DIALOGs = {}
        self.BITMAPs = {}
        self.MENUs = {}
        self.ICONs = {}
        self.STRINGTABLE = []
        while(self.i < self.nlines):
            line = self.getline()
            if len(line) == 0:
                continue
            if line[:2] == '  ':
                continue

            p = 0
            id1, p = split_next(line, ' ', ' ', p)
            name, p = split_next(line, ' ', ' ', p)
            #print(self.i, line)
            if name in ['DIALOGEX', 'DIALOG']:
                pp = self.parse_DIALOG(line, id1, p)
                if pp is not None:
                    if pp.id not in self.DIALOGs:
                        self.DIALOGs[pp.id] = pp
                else:
                    print('error:', self.i, line)

            elif name in ['MENU']:
                pp = self.parse_MENU(id1)
                if pp is not None:
                    if pp.id not in self.MENUs:
                        self.MENUs[pp.id] = pp
                else:
                    print('MENU error:', self.i, line)

            elif name in ['BITMAP']:
                aa = next_alltok(line, ' ', ' \r\n', p)
                assert len(aa) > 0
                if id1 not in self.BITMAPs:
                    self.BITMAPs[id1] = aa[-1]

            elif name in ['ICON']:
                aa = next_alltok(line, ' ', ' \r\n', p)
                assert len(aa) > 0
                if id1 not in self.ICONs:
                    self.ICONs[id1] = aa[-1]

            elif id1 in ['STRINGTABLE']:
                pp = self.parse_STRINGTABLE()
                self.STRINGTABLE += pp

    def print(self):
        for pp in self.DIALOGs:
            if pp is not None:
                pp.print()

    def c_str(self):
        out = ''
        pa, fn = os.path.split(self.fn)
        resdef = []
        ishex = True

        for k in self.BITMAPs:
            pp = self.BITMAPs[k].replace('"', '')
            p2 = os.path.join(pa, pp)
            out += file2str(p2, k, 'RES_TYPE_BITMAP', 16, ishex)
            resdef.append(k)

        for k in self.ICONs:
            pp = self.ICONs[k].replace('"', '')
            p2 = os.path.join(pa, pp)
            out += file2str(p2, k, 'RES_TYPE_ICON', 16, ishex)
            resdef.append(k)

        for k in self.MENUs:
            pp = self.MENUs[k]
            s = pp.c_str()
            name = pp.id
            vname = '__%s_MENU' % (name)
            out += 'static MenuParam %s[] = {\n%s};\n' % (vname, s)
            out += 'addRes(%s, %s, %s, countof(%s));\n' % ('RES_TYPE_MENU',
                                                           name, vname, vname)
            resdef += pp.get_ids()

        for k in self.DIALOGs:
            pp = self.DIALOGs[k]
            s = pp.c_str()
            name = pp.id
            vname = '__%s_DIALOG' % (name)
            out += 'static WindowParam %s[] = {\n%s};\n' % (vname, s)
            out += 'addRes(%s, %s, %s, countof(%s));\n' % ('RES_TYPE_DIALOG',
                                                           name, vname, vname)
            resdef += pp.get_ids()

        for k in self.STRINGTABLE:
            vname = '__%s_STRING' % (k.id)
            #out += 'static const char* %s = %s;\n' % (vname, k.text)
            out += 'addRes(RES_TYPE_STRING, %s, %s, %d);\n' % (k.id,
                                                               k.text, len(k.text)-2)
            resdef.append(k.id)

        resh = 'D:/code/git/ywlydd/cstd/include/mfc/res.h'
        resfile = ''
        if os.path.exists(resh):
            resfile = open(resh, 'r').read()
        defstr = '#include "resource.h"\n'
        resdef = list(set(resdef)-set(sysdef))
        resdef.sort()
        if False:
            for i in range(len(resdef)):
                d = resdef[i]
                if d in resfile:
                    continue
                if 'AFX_IDS' in d:
                    continue
                if 'ID_EDIT' in d:
                    continue
                if d.isdigit():
                    continue
                defstr += '#ifndef %s\n' % (d)
                defstr += '#define %s %d\n' % (d, (i+100))
                defstr += '#endif //%s\n' % (d)
        out = defstr + out
        return out


def rcmake(fn, ofn):
    pa = RcMake()
    pa.parse_file(fn)
    ss = pa.c_str()
    _, name = os.path.split(ofn)
    name = name.replace('.', '_')
    name = '__%s__' % name
    name = name.upper()
    li = [
        '#ifndef %s' % name,
        '#define %s' % name,
        '#include "dui/res.h"',
        ss,
        '#endif // %s' % name,
    ]
    ss = '\n'.join(li)
    # print(ss)
    tt = ss.encode('gbk', 'ignore')
    open(ofn, 'wb').write(tt)


def listdirall(root_path, exts=None):
    out = []
    if isinstance(exts, str):
        exts = exts.split(' ')

    for dir_name in os.listdir(root_path):
        file_path = os.path.join(root_path, dir_name)
        file_path = os.path.normpath(file_path)
        if os.path.isdir(file_path):
            out += listdirall(file_path, exts)
        else:
            _, ext = os.path.splitext(dir_name)
            if exts is None or ext in exts:
                out.append(file_path)

    return out


if __name__ == "__main__":
    pa = './'
    if len(sys.argv) > 1:
        pa = sys.argv[1]
        fns = listdirall(pa, '.rc')
    else:
        fns = [
            # './TrafficMonitor.rc'
            # 'D:/code/git/ywlydd/cstd/include/swc/Mdi/WinClass.rc',
            # 'D:/code/git/ywlydd/cstd/include/cximage/demo/demo.rc',
            # 'D:/code/git/ywlydd/cstd/include/duilib/test_app.rc',
            # 'D:/code/git/ywlydd/cstd/include/programming_windows_with_MFC/Shapes/Shapes.rc',
            # 'D:/code/git/ywlydd/cstd/include/programming_windows_with_MFC/Chap20/AutoPie/AutoPie.rc',
            #'D:/code/git/ywlydd/cstd/include/dui/XGroupBox/XGroupBoxTest.rc',
            'D:/code/git/ywlydd/cstd/include/dui/buttonst/CButtonST_Demo.rc',
        ]

    for fn in fns:
        print(fn)
        ofn = '%s.h' % fn
        rcmake(fn, ofn)
    print('-----')
